from keras import layers
from keras import models
from keras.preprocessing.image import ImageDataGenerator, DirectoryIterator
from tensorflow import optimizers

# 使用数据增强（扩充现有的数据样本）
# 数据增强
datagen = ImageDataGenerator(
    rotation_range=40,  # 整数，数据提升时图片随机转动的角度
    width_shift_range=0.2,  # 图片宽度的某个比例，数据提升时图片水平偏移的幅度`
    height_shift_range=0.2,  # 图片高度的某个比例，数据提升时图片竖直偏移的幅度
    shear_range=0.2,  # 剪切强度（逆时针方向的剪切变换角度）
    zoom_range=0.2,  # 随机缩放的幅度
    horizontal_flip=True,
    fill_mode='nearest')  # 当进行变换时超出边界的点将根据本参数给定的方法进行处理

#图片处理
tr_datagen = ImageDataGenerator(
    # rescale是图像缩放，是对图像的数值进行操作的的意思
    rescale=1. / 255,  # 图像数据值除以255  [0-255]-->[0-1]
    rotation_range=40,
    # 浮点数，图片宽度的某个比例，数据提升时图片水平偏移的幅度`
    width_shift_range=0.2,
    # 浮点数，图片高度的某个比例，数据提升时图片偏移的幅度
    height_shift_range=0.2,  # 浮点数或形如[lower,upper]的列表，随机缩放的幅度
    shear_range=0.2,  # 剪切强度（逆时针方向的剪切角度，以度为单位）
    zoom_range=0.2,  # 随机缩放范围
    #随机对图片执行水平翻转操
    horizontal_flip=True,)

vali_datagen = ImageDataGenerator(rescale=1. / 255)

tr_generator = tr_datagen.flow_from_directory(
    # 训练图像的目录
    r'C:\Users\s2096\PycharmProjects\pythonProject3\data\train',  # 目标目录的路径
    # 所有图像大小转换成150×150
    target_size=(150, 150),  # 目标大小
    batch_size=20,  # 批量数据的尺寸
    # 由于这是一个二元分类问题，y的label值也会被转换为二元标签
    # 返回标签数组的类型
    class_mode='binary')  # "binary" 将是 1D 二进制标签

vali_generator = vali_datagen.flow_from_directory(
    r'C:\Users\s2096\PycharmProjects\pythonProject3\data\validation',
    target_size=(150, 150),
    batch_size=20,
    #将根据目录下的子目录名称/结构自动推断类列表，一个子目录算一类。
    # 包含从类名到类索引的映射的字典可以通过属性class_indexes获得。
    class_mode='binary')


#构建网络
model = models.Sequential()
model.add(layers.Conv2D(16, (3, 3), activation='relu',  # 过滤器个数，卷积核尺寸，激活函数
                        # 输出形状
                        input_shape=(150, 150, 3)))  # 卷积层
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(32, (3, 3), activation='relu',  # 过滤器个数，卷积核尺寸，激活函数
                        # 输出形状
                        input_shape=(150, 150, 3)))  # 卷积层
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())  # 展开层，Flatten层用来将输入“压平”，即把多维的输入一维化，常用在从卷积层到全连接层的过渡
model.add(layers.Dropout(0.5))  #添加的层# 对数据增强后的数据集加上dropout层进行训练
model.add(layers.Dense(256, activation='relu'))  #输出空间维度，激活函数
model.add(layers.Dense(1, activation='sigmoid'))   # 全连接层，把卷积输出的二维特征图转化成一维的一个向量

model.compile(loss='binary_crossentropy',  #loss函数
              optimizer=optimizers.RMSprop(lr=1e-4),
              metrics=['acc'])
# 训练模型
history = model.fit_generator(
    tr_generator,
    steps_per_epoch=750,
    epochs=128,
    validation_data=vali_generator,
    validation_steps=125)
# 保存模型
model.save(r'C:\Users\s2096\PycharmProjects\pythonProject3/model3')
# 绘制图像
import matplotlib.pyplot as plt
acc = history.history['acc']  # 对象history的属性history的acc值
val_acc = history.history['val_acc']  # 对象history的属性history的val_acc值
loss = history.history['loss']  # 对象history的属性history的loss值
val_loss = history.history['val_loss']  # 对象history的属性history的val_loss值
epochs = range(len(acc))
#画图Training and validation accuracy
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
#Training and validation loss
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
